/* Speed curve tables for LOCKJAW, an implementation of the Soviet Mind Game

Copyright (C) 2006-2007 Damian Yerrick <tepples+lj@spamcop.net>

This work is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

Original game concept and design by Alexey Pajitnov.
The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg,
or The Tetris Company LLC.

*/

package fizzpicks.tetrapulp.tetrapulp;

import net.jscience.math.MathFP;

import java.util.EnumMap;
import java.util.Map;

/**
 * Created by will
 * Date: May 3, 2008 5:14:46 AM
 */
public class Speed {

    static final Map<LJSPD, Long> initialGravity = new EnumMap<LJSPD, Long>(LJSPD.class);

    static {
        initialGravity.put(LJSPD.RHYTHMZERO, 0L);
        initialGravity.put(LJSPD.RHYTHM, MathFP.toFP(20));
    }

    /**
     * The default speed curve for each gimmick.
     */
    static final Map<LJGM, LJSPD> defaultSpeedCurve = new EnumMap<LJGM, LJSPD>(LJGM.class);

    static {
        defaultSpeedCurve.put(LJGM.ATYPE, LJSPD.PLAYER_DEFAULT);
        defaultSpeedCurve.put(LJGM.BTYPE, LJSPD.PLAYER_DEFAULT);
        defaultSpeedCurve.put(LJGM.ULTRA, LJSPD.PLAYER_DEFAULT);
        defaultSpeedCurve.put(LJGM.DRILL, LJSPD.PLAYER_DEFAULT);
        defaultSpeedCurve.put(LJGM.ITEMS, LJSPD.PLAYER_DEFAULT);
        defaultSpeedCurve.put(LJGM.BABY, LJSPD.ZERO);
    }

/* The new speed curve code ****************************************/

    static int SCGRAV(int n, int d) {
        return ((n) * 2048 / (d));
    }

    enum SPEEDTYPE {
        SPEEDTYPE_RHYTHM,
        SPEEDTYPE_TGM,
        SPEEDTYPE_LINES,
        SPEEDTYPE_PIECES
    }

    /**
     * Unused struct, since it's just a simple ordered integer array.
     */
    static class LJSpeedStep {
        int level, gravity;
        int are, das, lock, line;

        LJSpeedStep(int[] i) {
            if (i != null && i.length >= 6) {
                this.level = i[0];
                this.gravity = i[1];
                this.are = i[2];
                this.das = i[3];
                this.lock = i[4];
                this.line = i[5];
            }
        }
    }

    public static final int SPD_LEVEL = 0;
    public static final int SPD_GRAVITY = 1;
    public static final int SPD_ARE = 2;
    public static final int SPD_DAS = 3;
    public static final int SPD_LOCK = 4;
    public static final int SPD_LINE = 5;

    static class LJSpeedCurve {
        String name;
        SPEEDTYPE type;
        int sectionLen;
        int nSteps;
        final int[][] steps;

        public LJSpeedCurve(String name, SPEEDTYPE type, int sectionLen, int nSteps, int[][] steps) {
            this.name = name;
            this.type = type;
            this.sectionLen = sectionLen;
            this.nSteps = nSteps;
            this.steps = steps;
        }
    }

    static final LJSpeedCurve scMaster = new LJSpeedCurve(
            "Master",
            SPEEDTYPE.SPEEDTYPE_TGM, 100,
            33,
            new int[][]{
                    {0, SCGRAV(4, 256), 25, 16, 30, 40},
                    {30, SCGRAV(6, 256), 25, 16, 30, 40},
                    {35, SCGRAV(8, 256), 25, 16, 30, 40},
                    {40, SCGRAV(10, 256), 25, 16, 30, 40},
                    {50, SCGRAV(12, 256), 25, 16, 30, 40},
                    {60, SCGRAV(16, 256), 25, 16, 30, 40},
                    {70, SCGRAV(32, 256), 25, 16, 30, 40},
                    {80, SCGRAV(48, 256), 25, 16, 30, 40},
                    {90, SCGRAV(64, 256), 25, 16, 30, 40},
                    {100, SCGRAV(80, 256), 25, 16, 30, 40},
                    {120, SCGRAV(96, 256), 25, 16, 30, 40},
                    {140, SCGRAV(112, 256), 25, 16, 30, 40},
                    {160, SCGRAV(128, 256), 25, 16, 30, 40},
                    {170, SCGRAV(144, 256), 25, 16, 30, 40},
                    {200, SCGRAV(4, 256), 25, 16, 30, 40},
                    {220, SCGRAV(32, 256), 25, 16, 30, 40},
                    {230, SCGRAV(64, 256), 25, 16, 30, 40},
                    {233, SCGRAV(96, 256), 25, 16, 30, 40},
                    {236, SCGRAV(128, 256), 25, 16, 30, 40},
                    {239, SCGRAV(160, 256), 25, 16, 30, 40},
                    {243, SCGRAV(192, 256), 25, 16, 30, 40},
                    {247, SCGRAV(224, 256), 25, 16, 30, 40},
                    {251, SCGRAV(1, 1), 25, 16, 30, 40},
                    {300, SCGRAV(2, 1), 25, 16, 30, 40},
                    {330, SCGRAV(3, 1), 25, 16, 30, 40},
                    {360, SCGRAV(4, 1), 25, 16, 30, 40},
                    {400, SCGRAV(5, 1), 25, 16, 30, 40},
                    {450, SCGRAV(3, 1), 25, 16, 30, 40},
                    {500, SCGRAV(20, 1), 25, 10, 30, 25},
                    {600, SCGRAV(20, 1), 25, 10, 30, 16},
                    {700, SCGRAV(20, 1), 16, 10, 30, 12},
                    {800, SCGRAV(20, 1), 12, 10, 30, 6},
                    {900, SCGRAV(20, 1), 12, 8, 17, 6}}
    );

    static final LJSpeedCurve scDeath = new LJSpeedCurve(
            "Death",
            SPEEDTYPE.SPEEDTYPE_TGM, 100,
            6,
            new int[][]{
                    {0, SCGRAV(20, 1), 18, 12, 30, 8},
                    {100, SCGRAV(20, 1), 14, 12, 26, 0},
                    {200, SCGRAV(20, 1), 14, 11, 22, 0},
                    {300, SCGRAV(20, 1), 8, 10, 18, 6},
                    {400, SCGRAV(20, 1), 7, 8, 15, 5},
                    {500, SCGRAV(20, 1), 6, 8, 15, 4}
            }
    );


    static final LJSpeedCurve scNES = new LJSpeedCurve(
            "NES",
            SPEEDTYPE.SPEEDTYPE_LINES, 10,
            15,
            new int[][]{
                    {0, SCGRAV(1, 48), 10, -1, 0, 30},
                    {10, SCGRAV(1, 43), 10, -1, 0, 30},
                    {20, SCGRAV(1, 38), 10, -1, 0, 30},
                    {30, SCGRAV(1, 33), 10, -1, 0, 30},
                    {40, SCGRAV(1, 28), 10, -1, 0, 30},
                    {50, SCGRAV(1, 23), 10, -1, 0, 30},
                    {60, SCGRAV(1, 18), 10, -1, 0, 30},
                    {70, SCGRAV(1, 13), 10, -1, 0, 30},
                    {80, SCGRAV(1, 8), 10, -1, 0, 30},
                    {90, SCGRAV(1, 6), 10, -1, 0, 30},
                    {100, SCGRAV(1, 5), 10, -1, 0, 30},
                    {130, SCGRAV(1, 4), 10, -1, 0, 30},
                    {160, SCGRAV(1, 3), 10, -1, 0, 30},
                    {190, SCGRAV(1, 2), 10, -1, 0, 30},
                    {290, SCGRAV(1, 1), 10, -1, 0, 30},
            }
    );

    static final LJSpeedCurve scGB = new LJSpeedCurve(
            "Game Boy",
            SPEEDTYPE.SPEEDTYPE_LINES, 10,
            18,
            new int[][]{
                    {0, SCGRAV(1, 53), 0, -1, 0, 90},
                    {10, SCGRAV(1, 49), 0, -1, 0, 90},
                    {20, SCGRAV(1, 45), 0, -1, 0, 90},
                    {30, SCGRAV(1, 41), 0, -1, 0, 90},
                    {40, SCGRAV(1, 37), 0, -1, 0, 90},
                    {50, SCGRAV(1, 33), 0, -1, 0, 90},
                    {60, SCGRAV(1, 28), 0, -1, 0, 90},
                    {70, SCGRAV(1, 22), 0, -1, 0, 90},
                    {80, SCGRAV(1, 17), 0, -1, 0, 90},
                    {90, SCGRAV(1, 11), 0, -1, 0, 90},
                    {100, SCGRAV(1, 10), 0, -1, 0, 90},
                    {110, SCGRAV(1, 9), 0, -1, 0, 90},
                    {120, SCGRAV(1, 8), 0, -1, 0, 90},
                    {130, SCGRAV(1, 7), 0, -1, 0, 90},
                    {140, SCGRAV(1, 6), 0, -1, 0, 90},
                    {160, SCGRAV(1, 5), 0, -1, 0, 90},
                    {180, SCGRAV(1, 4), 0, -1, 0, 90},
                    {200, SCGRAV(1, 3), 0, -1, 0, 90},
            }
    );

    static final LJSpeedCurve scZero = new LJSpeedCurve(
            "Zero",
            SPEEDTYPE.SPEEDTYPE_LINES, 10,
            1,
            new int[][]{
                    {0, SCGRAV(0, 1), -1, -1, -1, -1}
            }
    );

    static final LJSpeedCurve scExponential = new LJSpeedCurve(
            "Exponential",
            SPEEDTYPE.SPEEDTYPE_PIECES, 60,
            34,
            new int[][]{
                    {0, SCGRAV(1, 60), -1, -1, 40, -1},
                    {30, SCGRAV(1, 42), -1, -1, 40, -1},
                    {60, SCGRAV(2, 60), -1, -1, 40, -1},
                    {90, SCGRAV(2, 42), -1, -1, 40, -1},
                    {120, SCGRAV(4, 60), -1, -1, 40, -1},
                    {150, SCGRAV(4, 42), -1, -1, 40, -1},
                    {180, SCGRAV(8, 60), -1, -1, 40, -1},
                    {210, SCGRAV(8, 42), -1, -1, 40, -1},
                    {240, SCGRAV(16, 60), -1, -1, 40, -1},
                    {270, SCGRAV(16, 42), -1, -1, 40, -1},
                    {300, SCGRAV(32, 60), -1, -1, 40, -1},
                    {330, SCGRAV(32, 42), -1, -1, 40, -1},
                    {360, SCGRAV(64, 60), -1, -1, 40, -1},
                    {390, SCGRAV(64, 42), -1, -1, 40, -1},
                    {420, SCGRAV(128, 60), -1, -1, 40, -1},
                    {450, SCGRAV(128, 42), -1, -1, 40, -1},
                    {480, SCGRAV(256, 60), -1, -1, 40, -1},
                    {510, SCGRAV(256, 42), -1, -1, 40, -1},
                    {540, SCGRAV(512, 60), -1, -1, 40, -1},
                    {570, SCGRAV(512, 42), -1, -1, 40, -1},
                    {600, SCGRAV(20, 1), -1, -1, 40, -1},
                    {630, SCGRAV(20, 1), -1, -1, 30, -1},
                    {660, SCGRAV(20, 1), -1, -1, 24, -1},
                    {690, SCGRAV(20, 1), -1, -1, 20, -1},
                    {720, SCGRAV(20, 1), -1, -1, 17, -1},
                    {750, SCGRAV(20, 1), -1, -1, 15, -1},
                    {780, SCGRAV(20, 1), -1, -1, 13, -1},
                    {810, SCGRAV(20, 1), -1, -1, 12, -1},
                    {840, SCGRAV(20, 1), -1, -1, 11, -1},
                    {870, SCGRAV(20, 1), -1, -1, 10, -1},
                    {900, SCGRAV(20, 1), -1, -1, 9, -1},
                    {930, SCGRAV(20, 1), -1, -1, 8, -1},
                    {960, SCGRAV(20, 1), -1, -1, 7, -1},
                    {990, SCGRAV(20, 1), -1, -1, 6, -1}
            }
    );

    static final Map<LJSPD, LJSpeedCurve> newSpeedCurves = new EnumMap<LJSPD, LJSpeedCurve>(LJSPD.class);

    static {
        newSpeedCurves.put(LJSPD.EXP, scExponential);
        newSpeedCurves.put(LJSPD.ZERO, scZero);
        newSpeedCurves.put(LJSPD.TGM, scMaster);
        newSpeedCurves.put(LJSPD.DEATH, scDeath);
        newSpeedCurves.put(LJSPD.DEATH300, scDeath);
        newSpeedCurves.put(LJSPD.NES, scNES);
        newSpeedCurves.put(LJSPD.GB, scGB);
        newSpeedCurves.put(LJSPD.GBHEART, scGB);
    }

    /**
     * Performs a fast binary search of a speed step table.
     */
    static int[] getSpeedStep(final int[][] steps,
                              int nSteps, int level) {
        int lo = 0;
        int hi = nSteps;

        while (hi - lo > 1) {
            int mid = (hi + lo) / 2;
            int here = steps[mid][SPD_LEVEL];
            if (here == level) {
                return (steps[mid]);
            } else if (here < level) {
                lo = mid;
            } else {
                hi = mid;
            }
        }
        return (steps[lo]);
    }

    /**
     * Updates the level after each piece has retired.
     *
     * @return nonzero iff a new section has happened
     */
    static boolean updLevelAfterPiece(LJField p) {
        LJSPD curveID = p.speedState.curve;
        final LJSpeedCurve curve = newSpeedCurves.get(curveID);
        if (curve == null) {
            return false;
        }
        int sectionLen = curve.sectionLen;
        int oldLevel = p.speedState.level;
        int oldSection = oldLevel / sectionLen;
        int oldSectionPos = oldLevel % sectionLen;

        switch (curve.type) {
            case SPEEDTYPE_TGM:
                if (oldSectionPos + 1 >= sectionLen) {
                    return false;
                }
                // otherwise fall through to +1 per piece

            case SPEEDTYPE_PIECES:
                p.speedState.level = oldLevel + 1;
                break;

            default:
                return false;
        }

        int newSection = p.speedState.level / sectionLen;
        return newSection > oldSection;
    }

    /**
     * Updates the level after lines have been cleared.
     *
     * @return true iff a new section has happened
     */
    static boolean updLevelAfterLines(LJField p, int nLines) {
        LJSPD curveID = p.speedState.curve;
        final LJSpeedCurve curve = newSpeedCurves.get(curveID);
        if (curve == null) {
            return false;
        }
        int sectionLen = curve.sectionLen;
        int oldLevel = p.speedState.level;
        int oldSection = oldLevel / sectionLen;

        switch (curve.type) {
            case SPEEDTYPE_TGM:
            case SPEEDTYPE_LINES:
                p.speedState.level = oldLevel + nLines;
                break;

            default:
                return false;
        }

        int newSection = p.speedState.level / sectionLen;
        return newSection > oldSection;
    }

    static void setSpeedNew(LJField p, LJControl c) {
        LJSPD curveID = p.speedState.curve;
        final LJSpeedCurve curve = newSpeedCurves.get(curveID);
        if (curve == null) {
            return;
        }
        final int[] step =
                getSpeedStep(curve.steps, curve.nSteps, p.speedState.level);

        p.speed.gravity = step[SPD_GRAVITY] << 5;

        p.speed.entryDelay = step[SPD_ARE];
        if (p.speed.entryDelay > p.areStyle) {
            p.speed.entryDelay = p.areStyle;
        }

        if (step[SPD_DAS] > 0 && c.dasDelay > step[SPD_DAS]) {
            c.dasDelay = step[SPD_DAS];
        }

        if (p.setLockDelay >= 128) {
            p.speed.lockDelay = 127;
        } else if (p.setLockDelay > 0) {
            p.speed.lockDelay = p.setLockDelay;
        } else if (step[SPD_LOCK] > 0) {
            p.speed.lockDelay = step[SPD_LOCK];
        }

        if (p.setLineDelay > 0) {
            p.speed.lineDelay = p.setLineDelay;
        } else if (step[SPD_LINE] >= 0) {
            p.speed.lineDelay = step[SPD_LINE];
        } else {
            p.speed.lineDelay = p.speed.entryDelay;
        }
    }

/* Old speed curve system is below this line ***********************/


    static void initSpeed(LJField p) {
        if (defaultSpeedCurve.get(p.gimmick) != LJSPD.PLAYER_DEFAULT) {
            p.speedState.curve = defaultSpeedCurve.get(p.gimmick);
        }
        p.speed.gravity = MathFP.toFP(1);
//        p.speed.gravity = initialGravity.get(p.speedState.curve);

        switch (p.speedState.curve) {
            case RHYTHM:
            case RHYTHMZERO:
                p.speedState.level = 60;
                p.bpmCounter = 0;
                p.speedupCounter = 0;
                break;
            case TGM:
            case DEATH:
            case EXP:
                p.speedState.level = -1;
                break;
            case DEATH300:
                p.speedState.level = 300;
                break;
            case GBHEART:
                p.speedState.level = 100;
                break;
            default:
                p.speedState.level = 0;
                break;
        }
    }


    static void setSpeed(LJField p, LJControl c) {
        p.speed.entryDelay = p.areStyle;

        // Default line delay is equal to the entry delay,
        // but speed curves and setLineDelay can override this
        p.speed.lineDelay = p.speed.entryDelay;
        switch (p.speedState.curve) {

            case RHYTHM:
            case RHYTHMZERO:
                // If we've already banked five pieces' worth of time,
                // add 20 points instead of banking another.
                if (p.bpmCounter <= -18000) {
                    // removed in 0.21 because other curves don't reward for drops
                    // p->score += 20;
                } else {
                    p.bpmCounter -= 3600;  // number of frames per minute
                }
                p.speed.lockDelay = 3600 / p.speedState.level;
                p.speed.gravity = (p.speedState.curve == LJSPD.RHYTHM) ? MathFP.toFP(20) : 0;
                break;

            default:
                if (updLevelAfterPiece(p)) {
                    p.sounds.add(LJSND.SECTIONUP);
                }
                setSpeedNew(p, c);
                break;
        }

        if (p.setLockDelay >= 128) {
            p.speed.lockDelay = 127;
        } else if (p.setLockDelay > 0) {
            p.speed.lockDelay = p.setLockDelay;
        }
        if (p.setLineDelay > 0) {
            p.speed.lineDelay = p.setLineDelay;
  }
}


}
